package cn.apthink.calendar;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import lombok.Data;

@Data
public class DepartmentCalendar {
	private String id;
	private List<WorkDay> workDays;

	public DepartmentCalendar(String id) {
		this.id = id;
	}

	private void _sort() {
		workDays.sort((a, b) -> {
			return a.getCurrent().compareTo(b.getCurrent());
		});
	}

	public void addWorkDay(WorkDay workDay) {
		if (Objects.isNull(workDays)) {
			workDays = new ArrayList<WorkDay>();
		}
		workDays.add(workDay);
		_sort();
	}

	public void setWorkDays(List<WorkDay> workDays) {
		this.workDays = workDays;
		_sort();
	}

	public int indexOf(LocalDate current) {
		_validDate(current);
		return workDays.indexOf(new WorkDay(current));
	}

	public WorkDay get(int index) {
		return workDays.get(index);
	}

	public long duration() {
		return workDays.stream().mapToLong(WorkDay::duration).reduce(0, Long::sum);
	}

	public long durationNormal() {
		return workDays.stream().mapToLong(WorkDay::durationNormal).reduce(0, Long::sum);
	}

	public long durationOvertime() {
		return workDays.stream().mapToLong(WorkDay::durationOvertime).reduce(0, Long::sum);
	}

	private void _validDate(LocalDate current) {
		if (current.isBefore(getStartDate()) || current.isAfter(getEndDate()))
			throw new RuntimeException("超出工作日历范围");
	}

	private long _duration(LocalDate current, int type) {
		_validDate(current);
		Optional<WorkDay> o = workDays.stream().filter(x -> {
			return x.getCurrent().equals(current);
		}).findAny();
		if (o.isPresent())
			return type==0?o.get().duration():type==1?o.get().durationNormal():o.get().durationOvertime();
		else
			return 0;
	}
	
	public long duration(LocalDate current) {
		return _duration(current,0);
	}

	public long durationNormal(LocalDate current) {
		return _duration(current,1);
	}

	public long durationOvertime(LocalDate current) {
		return _duration(current,2);
	}

	public long duration(LocalDateTime start, LocalDateTime end) {
		int s = _validPosition(start.toLocalDate());
		int e = _validPosition(end.toLocalDate());
		if (start.isAfter(end))
			return 0;
		long result = 0;
		// 计算第一天时长只有部分
		result = get(s).calcDuration(start, end);
		s++;
		// 计算中间时长
		while (s < e) {
			result += get(s).duration();
			s++;
		}
		// 计算最后一天时长只有部分
		if (s <= e)
			result += get(s).calcDuration(start, end);
		return result;
	}

	public long duration(LocalDate start, LocalDate end) {
		int s = _validPosition(start);
		int e = _validPosition(end);
		if (start.isAfter(end))
			return 0;
		long result = 0;
		while (s <= e) {
			result += get(s).duration();
			s++;
		}
		return result;
	}

	public long capacity() {
		return workDays.stream().mapToLong(WorkDay::capacity).reduce(0, Long::sum);
	}

	public long capacityNormal() {
		return workDays.stream().mapToLong(WorkDay::capacityNormal).reduce(0, Long::sum);
	}

	public long capacityOvertime() {
		return workDays.stream().mapToLong(WorkDay::capacityOvertime).reduce(0, Long::sum);
	}

	private long _capacity(LocalDate current, int type) {
		_validDate(current);
		Optional<WorkDay> o = workDays.stream().filter(x -> {
			return x.getCurrent().equals(current);
		}).findAny();
		if (o.isPresent())
			return type==0?o.get().capacity():type==1?o.get().capacityNormal():o.get().capacityOvertime();
		else
			return 0;
	}

	public long capacity(LocalDate current) {
		return _capacity(current,0);
	}

	public long capacityNormal(LocalDate current) {
		return _capacity(current,1);
	}

	public long capacityOvertime(LocalDate current) {
		return _capacity(current,2);
	}

	public long capacity(LocalDateTime start, LocalDateTime end) {
		int s = _validPosition(start.toLocalDate());
		int e = _validPosition(end.toLocalDate());
		if (start.isAfter(end))
			return 0;
		long result = 0;
		// 计算第一天时长只有部分
		result = get(s).calcCapacity(start, end);
		s++;
		// 计算中间时长
		while (s < e) {
			result += get(s).capacity();
			s++;
		}
		// 计算最后一天时长只有部分
		if (s <= e)
			result += get(s).calcCapacity(start, end);
		return result;
	}

	public long capacity(LocalDate start, LocalDate end) {
		int s = _validPosition(start);
		int e = _validPosition(end);
		if (start.isAfter(end))
			return 0;
		long result = 0;
		while (s <= e) {
			result += get(s).capacity();
			s++;
		}
		return result;
	}

	public LocalDate getStartDate() {
		return workDays.get(0).getCurrent();
	}

	public LocalDate getEndDate() {
		return workDays.get(workDays.size() - 1).getCurrent();
	}

	private int _validPosition(LocalDate current) throws RuntimeException {
		return _validPosition(current, true);
	}

	private int _validPosition(LocalDate current, boolean direction) throws RuntimeException {
		if (current.isAfter(getEndDate()) || current.isBefore(getStartDate()))
			throw new RuntimeException("超出工作日历范围");
		WorkDay currentDay = new WorkDay(current);
		int index = workDays.indexOf(currentDay);
		if (direction) {
			while (true) {
				WorkDay d = workDays.get(index);
				if (d.isWork())
					return index;
				if (++index > workDays.size() - 1)
					throw new RuntimeException("超出工作日历范围");
			}
		} else {
			while (true) {
				WorkDay d = workDays.get(index);
				if (d.isWork())
					return index;
				if (--index < 0)
					throw new RuntimeException("超出工作日历范围");
			}
		}
	}

	public LocalDateTime getValidWorkTime(LocalDateTime current) throws RuntimeException {
		return getValidWorkTime(current, true);
	}

	public LocalDateTime getValidWorkTime(LocalDateTime current, boolean direction) throws RuntimeException {
		int position = _validPosition(current.toLocalDate(), direction);
		return workDays.get(position).getValidWorkTime(current);
	}

	public WorkDay getValidWorkDay(LocalDate current) throws RuntimeException {
		return getValidWorkDay(current, true);
	}

	public WorkDay getValidWorkDay(LocalDate current, boolean direction) throws RuntimeException {
		int position = _validPosition(current, direction);
		return workDays.get(position);
	}

	public WorkDay getValidWorkDay(LocalDate current, int offday) throws RuntimeException {
		return getValidWorkDay(current, offday, true);
	}

	public WorkDay getValidWorkDay(LocalDate current, int offday, boolean direction) throws RuntimeException {
		int index = _validPosition(current, direction);
		int days = Math.abs(offday);
		WorkDay result = workDays.get(index);
		if (offday == 0)
			return result;
		if (offday > 0) {
			while (days > 0) {
				if (++index > workDays.size() - 1)
					throw new RuntimeException("超出工作日历范围");
				result = workDays.get(index);
				if (result.isWork()) {
					days--;
				}
			}
		} else {
			while (days > 0) {
				if (--index < 0)
					throw new RuntimeException("超出工作日历范围");
				result = workDays.get(index);
				if (result.isWork()) {
					days--;
				}
			}
		}
		return result;
	}

	/**
	 * 采用时长计算（不考虑每日人数）
	 * @param startTime
	 * @param duration
	 * @return
	 * @throws RuntimeException
	 */
	public LocalDateTime calcEndTime(LocalDateTime startTime, int duration) throws RuntimeException {
		int index = _validPosition(startTime.toLocalDate(), true);
		WorkDay workDay = workDays.get(index);
		long c = workDay.calcDuration(startTime);
		int l = duration;
		while (c<l) {
			l -= c;
			if (++index > workDays.size() - 1)
				throw new RuntimeException("超出工作日历范围");
			workDay = workDays.get(index);
			c = workDay.duration();
		}
		//David: 多于5秒才进行下一天计算
		if (l>5)
			return workDay.calcEndTime(startTime, l);
		else {
			while (!workDays.get(--index).isWork()) {
				//David: 避免回退到非工作日
			}
			return workDays.get(index).getEndTime();
		}
	}

	/**
	 * 采用负载计算（考虑每日人数）
	 * @param startTime
	 * @param loadding
	 * @return
	 * @throws RuntimeException
	 */
	public LocalDateTime calcEndTimeByLoadding(LocalDateTime startTime, long loadding) throws RuntimeException {
		int index = _validPosition(startTime.toLocalDate(), true);
		WorkDay workDay = workDays.get(index);
		long c = workDay.calcCapacity(startTime);
		long l = loadding;
		while (c<l) {
			l -= c;
			if (++index > workDays.size() - 1)
				throw new RuntimeException("超出工作日历范围");
			workDay = workDays.get(index);
			c = workDay.capacity();
		}
		//David: 负载多于5秒*人数才进行下一天计算
		if (l>workDay.getWorker()*5)
			return workDay.calcEndTime(startTime, l);
		else {
			while (!workDays.get(--index).isWork()) {
				//David: 避免回退到非工作日
			}
			return workDays.get(index).getEndTime();
		}
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		DepartmentCalendar other = (DepartmentCalendar) obj;
		return Objects.equals(id, other.id);
	}

	@Override
	public int hashCode() {
		return Objects.hash(id);
	}

	public static DepartmentCalendar testData() {
		List<WorkDay> lstWorkDay = new ArrayList<WorkDay>();
		LocalDate current = LocalDate.now();
		WorkDay workDay = WorkDay.testData();
		workDay.setWorker(2);
		workDay.setCurrent(current);
		lstWorkDay.add(workDay);
		
		workDay = WorkDay.testData();
		workDay.setWorker(2);
		workDay.setCurrent(current.plusDays(1));
		lstWorkDay.add(workDay);

		workDay = WorkDay.testData();
		workDay.setWorker(2);
		workDay.setCurrent(current.plusDays(2));
		lstWorkDay.add(workDay);

		workDay = WorkDay.testData();
		workDay.setWorker(0);
		workDay.setCurrent(current.plusDays(3));
		lstWorkDay.add(workDay);

		workDay = WorkDay.testData();
		workDay.setWorker(1);
		workDay.setCurrent(current.plusDays(4));
		lstWorkDay.add(workDay);

		WorkDay workDay2 = new WorkDay(current.plusDays(5));
		workDay2.setWorker(2);
		lstWorkDay.add(workDay2);

		DepartmentCalendar departmentCalendar = new DepartmentCalendar("line1");
		departmentCalendar.setWorkDays(lstWorkDay);
		return departmentCalendar;
	}

}
